﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Google.GData.Client;
using Google.Contacts;
using Google.Documents;
using Google.GData.Documents;
using Google.GData.AccessControl;
using Google.GData.Extensions;
using DataType;
using RemoteConnector;
using System.Data.SqlClient;
using System.IO;
using System.Diagnostics;
using System.ComponentModel;

namespace GoogleConnector
{
    public class Connector : IRemoteConnector
    {
        //A connection with the DocList API.
        private RequestSettings m_Settings;
        public DocumentsService m_Service {get; private set;}

        private static bool HeaderMatch(string header)
        {
            return header.Contains("If-");
        }

        public void Login(Object sender, Credentials cred)
        {
            GDataGAuthRequestFactory authFactory = new GDataGAuthRequestFactory("writely", "UpriseApps-OfficeInCloud-v1"); 
            authFactory.AccountType = "HOSTED";
            m_Service = new DocumentsService(authFactory.ApplicationName);
            m_Service.Credentials = cred.m_Cred;
            ((GDataRequestFactory)m_Service.RequestFactory).KeepAlive = false;

            m_Service.QueryAuthenticationToken(); // Authenticate the user immediately

            m_Settings = new RequestSettings("OfficeInCloud", cred.m_Cred);
            m_Settings.AutoPaging = true;
            m_Settings.PageSize = 100;
            m_Settings.Timeout = -1; 
         }

        public void LogOut()
        {
        }

        public void GetContacts(Object sender, FeedWrapper<Contact> feed)
        {
            ContactsRequest request = new ContactsRequest(m_Settings);
            feed.m_Feed = request.GetContacts();
        }

        public void GetDocuments(Object sender, FeedWrapper<Document> feed)
        {
            DocumentsRequest request = new DocumentsRequest(m_Settings);
            feed.m_Feed = request.GetEverything(); 
        }

        public void GetFolders(Object sender, FeedWrapper<Document> feed)
        {
            DocumentsRequest request = new DocumentsRequest(m_Settings);
            feed.m_Feed = request.GetFolders();
        }

        public void UploadDocument(Object sender, DocumentInfo doc)
        {
            DateTime modstamp = DateTime.Now;

            ((GDataGAuthRequestFactory)m_Service.RequestFactory).NumberOfRetries = 10;
            ((GDataRequestFactory)m_Service.RequestFactory).CustomHeaders.RemoveAll(HeaderMatch);
            //GDataLoggingRequestFactory factory = new GDataLoggingRequestFactory(DocumentsService.GDocumentsService, "DocListUploader");
            //factory.MethodOverride = true;
            //m_Service.RequestFactory = factory;

            doc.m_DocEntry = m_Service.UploadDocument(doc.m_FullName, doc.m_Name);

            //factory.MethodOverride = false;
   
            System.Threading.Thread.Sleep(2000);
            DocumentsListQuery query = new DocumentsListQuery();
            query.ModifiedSince = modstamp;
            query.Title = doc.m_Name;
            query.TitleExact = true;
            
            DocumentsFeed feed = m_Service.Query(query);
            foreach (DocumentEntry d in feed.Entries)
            {
                if (!d.Id.AbsoluteUri.Equals(doc.m_DocEntry.Id.AbsoluteUri))
                {
                    d.Delete();
                }
            }
        }

        public void UpdateDocument(Object sender, DocumentInfo doc)
        {
            FileStream stream = null;
            GDataLoggingRequestFactory factory = null;
            try
            {
                if (doc.m_DocEntry != null)
                {
                    FileInfo fileInfo = new FileInfo(doc.m_FullName);
                    stream = fileInfo.Open(FileMode.Open, FileAccess.Read, FileShare.ReadWrite);

                    Uri postUri = new Uri(doc.m_DocEntry.MediaUri.Content);

                    string ext = fileInfo.Extension.ToUpper().Substring(1);
                    String contentType = (String)DocumentsService.GDocumentsAllowedTypes[ext];

                    //GDataLoggingRequestFactory factory1 = new GDataLoggingRequestFactory(DocumentsService.GDocumentsService, "DocListUploader");
                    //factory1.MethodOverride = true;
                    //m_Service.RequestFactory = factory1;

                    // this part allows us to find if document was already updated by other users - 
                    //"412 precondition failed"
                    ((GDataGAuthRequestFactory)m_Service.RequestFactory).NumberOfRetries = 0;

                    ((GDataRequestFactory)m_Service.RequestFactory).CustomHeaders.RemoveAll(HeaderMatch);
                    ((GDataRequestFactory)m_Service.RequestFactory).CustomHeaders.Add(GDataRequestFactory.IfMatch + ":" + doc.m_DocEntry.Etag);

                    doc.m_DocEntry = m_Service.Update(postUri, stream, contentType, doc.m_Name) as DocumentEntry;

                    //factory1.MethodOverride = false;
                }
            }
            finally
            {
                if (stream != null) stream.Close();
                if (factory != null) factory.MethodOverride = false;
            }
        }

        public void GetDocumentAcl(Object sender, ShareInfoCollection info)
        {
            DocumentsFeed feed = null;
            AclFeed aclF = null;

            ((GDataGAuthRequestFactory)m_Service.RequestFactory).NumberOfRetries = 100;
            ((GDataRequestFactory)m_Service.RequestFactory).CustomHeaders.RemoveAll(HeaderMatch);
            ((GDataRequestFactory)m_Service.RequestFactory).CustomHeaders.Add(GDataRequestFactory.IfMatch + ":*");
            feed = (DocumentsFeed)m_Service.Query(new AclQuery(info.m_Doc.AccessControlList));
            aclF = new AclFeed(new Uri("http://schemas.google.com/g/2005#batch"), m_Service);


            foreach (DocumentEntry dc in feed.Entries)
            {
                AclEntry aclE = new AclEntry();

                foreach (IExtensionElementFactory ex in dc.ExtensionElements)
                {
                    //the <role> and <scope> are put in the ExtensionElements of DocumentFeed.Entries
                    // unfortunately there is no clear way to get AclFeed from it
                    if (ex is XmlExtension)
                    {
                        XmlExtension xmlEx = (XmlExtension)ex;
                        if (xmlEx.XmlName == "role")
                            aclE.Role = new AclRole(xmlEx.Node.Attributes["value"].Value);
                        else if (xmlEx.XmlName == "scope")
                        {
                            aclE.Scope = new AclScope();
                            aclE.Scope.Type = xmlEx.Node.Attributes["type"].Value;
                            aclE.Scope.Value =  xmlEx.Node.Attributes["value"].Value;
                        }
                    }
                }
                aclF.Entries.Add(aclE);
            }
            
            info.m_Feed = aclF;
        }

        public AclFeed RetriveDocumentAcl(ShareInfoCollection info)
        {
            DocumentsFeed feed = (DocumentsFeed)m_Service.Query(new AclQuery(info.m_Doc.AccessControlList));
            AclFeed aclF = new AclFeed(new Uri("http://schemas.google.com/g/2005#batch"), m_Service);

            foreach (DocumentEntry dc in feed.Entries)
            {
                AclEntry aclE = new AclEntry();

                foreach (IExtensionElementFactory ex in dc.ExtensionElements)
                {
                    //the <role> and <scope> are put in the ExtensionElements of DocumentFeed.Entries
                    // unfortunately there is no clear way to get AclFeed from it
                    if (ex is XmlExtension)
                    {
                        XmlExtension xmlEx = (XmlExtension)ex;
                        if (xmlEx.XmlName == "role")
                            aclE.Role = new AclRole(xmlEx.Node.Attributes["value"].Value);
                        else if (xmlEx.XmlName == "scope")
                        {
                            aclE.Scope = new AclScope();
                            aclE.Scope.Type = xmlEx.Node.Attributes["type"].Value;
                            aclE.Scope.Value = xmlEx.Node.Attributes["value"].Value;
                        }
                    }
                }
                aclF.Entries.Add(aclE);
            }

            return aclF;
        }


        public void DeleteSharedUsers(DocumentEntry entry)
        {
            ShareInfoCollection info = new ShareInfoCollection(entry);
            GetDocumentAcl(null, info);
            foreach (AclEntry acl in info.m_Feed.Entries)
                acl.BatchData = new GDataBatchEntryData(GDataBatchOperationType.delete);

            //delete all current shared users
            UpdateSharedUsers(info);
        }

        private void UpdateSharedUsers(ShareInfoCollection info)
        {
            //info.m_Feed.Entries contain only patform-independent info about records
            // so we build complete Feed here
            if (info.m_Doc.AccessControlList == null) return;

            AclFeed aclOriginal = RetriveDocumentAcl(info); ;

            AclFeed batchFeed = new AclFeed(new Uri("http://schemas.google.com/g/2005#batch"), m_Service);
            batchFeed.Categories.Add(new AtomCategory("http://schemas.google.com/acl/2007#accessRule",
                                                                   "http://schemas.google.com/g/2005#kind"));
                   
            foreach (AclEntry acl in info.m_Feed.Entries)
                if (acl.Role.Value != "owner")
                {
                    AclEntry aclIn = acl;

                    foreach (AclEntry entryOriginal in aclOriginal.Entries)
                    {
                        if (entryOriginal.Scope.Value.Equals(acl.Scope.Value))
                            aclIn.BatchData = new GDataBatchEntryData(GDataBatchOperationType.update);
                    }

                    if (aclIn.BatchData == null)
                         aclIn.BatchData = new GDataBatchEntryData(GDataBatchOperationType.insert);
                    
                 
                    // it might be required to change '@' to '3A'
                    aclIn.Id = new AtomId(info.m_Doc.AccessControlList + "/user%3A" + aclIn.Scope.Value);
                    
                    batchFeed.Entries.Add(aclIn);
                }

            Uri aclUri = new Uri(info.m_Doc.AccessControlList + "/batch");

            m_Service.Batch(batchFeed, aclUri);
        }

        public void ShareDocument(Object sender, ShareInfoCollection info)
        {
            UpdateSharedUsers(info);
        }

        public void DownloadDocument(object sender, DownloadInfo info)
        {
            DocumentsRequest request = new DocumentsRequest(m_Settings);
            info.m_Stream = request.Download(info.m_Doc, info.m_type); 
        }

        public void OpenDocumentInCloud(object sender, DocumentInfo info)
        {
            try
            {
                Process.Start(info.m_DocEntry.AlternateUri.ToString());
            }
            catch (Win32Exception)
            {
                //nothing is registered to handle URLs, so let's use IE!
                Process.Start("IExplore.exe", info.m_DocEntry.AlternateUri.ToString());
            } 
        }

        public void LookForDocument(Object sender, DocumentInfo info)
        {
            ((GDataGAuthRequestFactory)m_Service.RequestFactory).NumberOfRetries = 0;
            ((GDataRequestFactory)m_Service.RequestFactory).CustomHeaders.RemoveAll(HeaderMatch);
            ((GDataRequestFactory)m_Service.RequestFactory).CustomHeaders.Add(GDataRequestFactory.IfMatch + ":*");
            ((GDataRequestFactory)m_Service.RequestFactory).CustomHeaders.Add(GDataRequestFactory.IfNoneMatch + ":" + Guid.NewGuid().ToString());

            DocumentsListQuery query = new DocumentsListQuery(info.m_RemoteId);
            DocumentsFeed feed = m_Service.Query(query);
            info.m_DocEntry = (DocumentEntry)feed.Entries[0];
        }
    }
}
